package cloudspace.util.jassistwrappers;

import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import cloudspace.stats.StatEvent;
import cloudspace.stats.StatisticsService;
import cloudspace.util.WeakIdentityHashMap;


/**
 * The Class VMFile. This file represents all of the static methods used to wrap
 * and rewrite file calls. This wrapper implements a Map from files to user path
 * names for retrieval later. It inherits from {@link VMClassContext} to allow
 * access to local path information.
 * 
 * It is important to note that all files created in any form must be logged.
 * This includes files created from api calls. If every file created is not
 * logged, then the entire system will fail.
 */
public class VMFile extends VMClassContext
{
    protected static Logger logger = Logger.getLogger( VMFile.class );

    /** The user passed path map. */
    static private WeakIdentityHashMap<File, String> userPassedPathMap = new WeakIdentityHashMap<File, String>();


    /**
     * This function logs a list of files created from the File.list functions.
     * 
     * @param parent
     *            the parent file that the list of file was created using
     * @param list
     *            the list of files attached to the parent file
     */
    public static void logFileList( File parent, File[] list )
    {
        if ( list != null )
        {
            for ( int i = 0; i < list.length; i++ )
            {
                File elem = list[i];
                if ( getUserPassedString( elem ) == null )
                {
                    logFileWithParent( parent, elem );
                }
            }
        }
    }


    /**
     * Log a file created from a parent file.
     * 
     * @param parent
     *            the parent file
     * @param elem
     *            the newly created file
     */
    public static void logFileWithParent( File parent, File elem )
    {
        String prefix = getCurrentPrefix();
        boolean abs = isFileAbsolute( parent );
        if ( abs )
        {
            logUserPassedString( elem,
                elem.getAbsolutePath().substring( prefix.length() - 1 ) );
        }
        else
        {
            String tempShort = elem.getPath().substring( prefix.length() );
            logUserPassedString( elem, tempShort );
        }
    }


    /**
     * Log a file according to a path passed to new File from a user
     * 
     * @param newFile
     *            the newly created file
     * @param userPath
     *            the path passed to new File from the user.
     */
    public static void logUserPassedString( File newFile, String userPath )
    {
        StatisticsService.getStatService()
            .observe( new StatEvent( "FilesOpened" ) );
        userPassedPathMap.put( newFile, userPath );
    }


    /**
     * using a file as a reference, the path used to create a file is returned.
     * The path returned might not have been passed by the user. It might have
     * been created on the fly by VMFile according to a parent file or some
     * other heuristics.
     * 
     * @param lookupFile
     *            the file to lookup
     * 
     * @return the string used to instanciate the file.
     */
    public static String getUserPassedString( File lookupFile )
    {
        String s = userPassedPathMap.get( lookupFile );

        return s;
    }


    /**
     * Using a file name, VMFile will translate the filename to an absolute file
     * name according to the proper location in the filesystem.
     * 
     * @param fname
     *            The file name to translate to an absolute file name
     * 
     * @return the true absolute filename
     */
    public static String toGlobalName( String fname )
    {
        logger.trace( "Rewriting path " + fname );
        String prefix = getCurrentPrefix();
        fname = stripRoot( fname );
        fname = cleanWindowsPath( fname );
        fname = fname.replace( '/', File.separatorChar );
        if ( fname != null )
        {
            return prefix + fname;
        }
        else
        {
            return fname;
        }
    }


    private static String cleanWindowsPath( String path )
    {
        return path.replace( '\\', '/' );
    }


    public static String toRawGlobalName( String fname )
    {
        return getCurrentPrefix() + fname;
    }


    private static String stripRoot( String path )
    {
        if ( hasWindowsDriveLetter( path ) )
        {
            return path.substring( 3 );
        }
        if ( path.startsWith( "/" ) )
        {
            return path.substring( 1 );
        }
        return path;
    }


    /**
     * This function checks if the file created by the user is actually
     * absolute. This looks up the user passed string in an attempt to reliably
     * provide information about absoluteness of the original file.
     * 
     * @param testFile
     *            The file to check for absoluteness
     * 
     * @return true, if the file is absolute
     */
    public static boolean isFileAbsolute( File testFile )
    {
        String userPassed = getUserPassedString( testFile );
        return isAbsolute( userPassed );
    }


    /**
     * This function checks a path for absoluteness. Specifically, this function
     * looks for a path that either starts with / or /[a-zA-Z]:/
     * 
     * @param path
     *            the to check for absoluteness
     * 
     * @return true, if is absolute
     */
    public static boolean isAbsolute( String path )
    {

        if ( path.startsWith( "/" ) || path.startsWith( "\\" ) )
        {
            return true;
        }
        return isWindowsAbsolute( path );
    }


    private static boolean isWindowsAbsolute( String path )
    {
        Pattern p = Pattern.compile( "[a-zA-Z]://.*" );
        Matcher m = p.matcher( path );
        return m.matches();
    }


    private static boolean hasWindowsDriveLetter( String path )
    {
        Pattern p = Pattern.compile( "[a-zA-Z]:/.*" );
        Matcher m = p.matcher( path );
        return m.matches();
    }


    /**
     * This function returns the user local name of a string according to a
     * file. In other words, this string is what should be presented to the user
     * when the user has a stirng produced from a File.
     * 
     * @param curFile
     *            the file to check
     * @param fname
     *            the file name that is related to the curFile
     * 
     * @return the local name of the path that should be presented to the user.
     */
    public static String toLocalName( File curFile, String fname )
    {
        if ( fname == null )
            return fname;
        String prefix = getCurrentPrefix();
        fname = fname.replace( '\\', '/' );
        String userPassed = getUserPassedString( curFile );
        if ( userPassed == null )
        {
            System.out.println( "could not find " + curFile );
            return fname;
        }
        if ( userPassed.equals( "" ) )
        {
            return "";
        }
        if ( userPassed.equals( "/" ) )
        {
            return "/";
        }
        fname = fname.substring( prefix.length() );
        // TODO: This could be flawed if a student has overwritten getPath()
        // this will be broken
        if ( isAbsolute( userPassed ) && !fname.startsWith( "/" ) )
        {
            fname = "/" + fname;
        }

        return fname;
    }


    /**
     * This returns the local absolute name of a file. Meaning if a user asked
     * for the absolute name of a file, the should expect to see this value
     * 
     * @param fname
     *            the path to be converted
     * 
     * @return absolute path as seen by the user
     */
    public static String toLocalAbsName( File curFile, String fname )
    {
        String userPassed = getUserPassedString( curFile );
        fname = fname.replace( '\\', '/' );
        String prefix = getCurrentPrefix();
        if ( fname == null )
        {
            return null;
        }
        if ( userPassed.equals( "" ) )
        {
            return "/";
        }
        String cleaned = fname.substring( prefix.length() );
        if ( !cleaned.startsWith( "/" ) || !isWindowsAbsolute( cleaned ) )
        {
            cleaned = "/" + cleaned;
        }
        return cleaned;
    }


    /**
     * This function checks for the equality of two files
     * 
     * @param file1
     *            the first file
     * @param file2
     *            the second file
     * 
     * @return the integer representing equality.
     */
    public static int compareFiles( File file1, File file2 )
    {
        File userFile1 = new File( getUserPassedString( file1 ) );
        File userFile2 = new File( getUserPassedString( file2 ) );

        return userFile1.compareTo( userFile2 );
    }


    /**
     * This is a convinence function for logging a file that was produced from
     * an api call.
     * 
     * @param apiFile
     *            the File produced from an api call.
     */
    public static void logNewAPIFile( File apiFile )
    {
        String prefix = getCurrentPrefix();
        if ( getUserPassedString( apiFile ) == null )
            userPassedPathMap.put( apiFile, apiFile.getAbsolutePath()
                .substring( prefix.length() - 1 ) );
    }


    public static File[] cleanFileList( File[] fileList )
    {
        File[] files = new File[fileList.length];
        int index = 0;
        for ( File myFile : fileList )
        {
            String userPassed = getUserPassedString( myFile );
            if ( userPassed != null )
            {
                files[index] = myFile;
            }
            else
            {
                File cleanedFile = new File( toGlobalName( myFile.getPath() ) );
                logUserPassedString( cleanedFile, myFile.getPath() );
                files[index] = cleanedFile;
            }
        }
        return files;
    }


    public static String fixGetName( String origReturnValue, File f )
    {
        if ( f == null )
            return origReturnValue;
        String userpassed = getUserPassedString( f );
        if ( userpassed.equals( "/" ) || userpassed.equals( "" ) )
        {
            return "";
        }
        return origReturnValue;
    }


    public static String getParent( File myFile )
    {
        String parentPath = myFile.getParent();
        // if (myFile == null)
        // return parentPath;
        String userpassed = getUserPassedString( myFile );
        if ( userpassed.equals( "" ) )
        {
            return null;
        }
        if ( userpassed.equals( "/" ) )
        {
            return null;
        }
        // if (myFile.getName().equals(getCurrentPrefix()))
        // {
        // return null;
        // }
        if ( parentPath.equals( getCurrentPrefix().substring( 0,
            getCurrentPrefix().length() - 1 ) ) )
            return null;
        return toLocalName( myFile, parentPath );
    }


    public static File getParentFile( File myFile, File parentFile )
    {
        if ( parentFile == null )
            return null;
        String parentPath = getParent( myFile );
        if ( parentPath == null )
        {
            return null;
        }
        logUserPassedString( parentFile, parentPath );
        return parentFile;
    }


    public static String[] cleanPaths( File f, String[] paths )
    {
        if ( paths == null )
        {
            return paths;
        }
        String[] newPath = new String[paths.length];
        String userpassed = getUserPassedString( f );
        boolean isAbsolute = isAbsolute( userpassed );
        int index = 0;
        for ( String path : paths )
        {
            path = path.substring( getCurrentPrefix().length() );
            if ( isAbsolute )
                path = "/" + path;
            newPath[index] = path;
            index++;
        }
        return newPath;
    }


    public static String[] globalizePaths( String[] paths )
    {
        for ( int i = 0; i < paths.length; i++ )
        {
            paths[i] = getCurrentPrefix() + paths[i];
        }
        return paths;
    }
}